package com.matrix.cart.service.impl;

import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.matrix.api.cart.dto.CartItemDTO;
import com.matrix.api.cart.vo.AppliedCouponInfo;
import com.matrix.api.cart.vo.CartItemVO;
import com.matrix.api.cart.vo.CartResultVO;
import com.matrix.api.marketing.MarketingApi;
import com.matrix.api.marketing.vo.UserCouponVO;
import com.matrix.api.product.ProductApi;
import com.matrix.api.product.vo.ProductVO;
import com.matrix.api.product.vo.SpecOptionVO;
import com.matrix.cart.config.ThreadPoolConfig;
import com.matrix.cart.model.CartItemValue;
import com.matrix.cart.service.CartService;
import com.matrix.common.constant.RedisConstants;
import com.matrix.common.exception.BusinessException;
import lombok.extern.slf4j.Slf4j;
import org.apache.dubbo.config.annotation.DubboReference;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Service;
import org.springframework.util.CollectionUtils;

import java.math.BigDecimal;
import java.util.*;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.Executor;
import java.util.concurrent.TimeUnit;
import java.util.function.Function;
import java.util.stream.Collectors;

/**
 * @author 有点甜
 * @since 2025/8/8
 */
@Service
@Slf4j
public class CartServiceImpl implements CartService {

    private final StringRedisTemplate redisTemplate;
    private final ObjectMapper objectMapper;
    private final Executor asyncExecutor;

    @DubboReference(check = false, timeout = 3000)
    private ProductApi productApi;

    @DubboReference(check = false, timeout = 3000)
    private MarketingApi marketingApi;

    private static final long CART_TTL_DAYS = 7;
    private static final int MAX_CART_ITEMS = 50;

    public CartServiceImpl(StringRedisTemplate redisTemplate,
                           ObjectMapper objectMapper,
                           @Qualifier(ThreadPoolConfig.ASYNC_EXECUTOR_NAME) Executor asyncExecutor) {
        this.redisTemplate = redisTemplate;
        this.objectMapper = objectMapper;
        this.asyncExecutor = asyncExecutor;
    }

    @Override
    public void addItem(Long userId, CartItemDTO itemDTO) {
        if (itemDTO.getQuantity() <= 0) {
            throw new BusinessException("添加数量必须为正数");
        }

        String cartKey = RedisConstants.buildKey(RedisConstants.CART_KEY_PATTERN, userId, itemDTO.getShopId());
        String fieldKey = buildFieldKey(itemDTO);

        ProductVO product = getProductSnapshot(itemDTO.getProductId());
        PriceAndSpecResult priceResult = calculatePriceAndSpec(product, itemDTO.getSpecOptionIds());

        try {
            String valueJson = (String) redisTemplate.opsForHash().get(cartKey, fieldKey);
            CartItemValue cartItemValue;

            if (valueJson != null) {
                cartItemValue = objectMapper.readValue(valueJson, CartItemValue.class);
                cartItemValue.setQuantity(cartItemValue.getQuantity() + itemDTO.getQuantity());
            } else {
                if (redisTemplate.opsForHash().size(cartKey) >= MAX_CART_ITEMS) {
                    throw new BusinessException("购物车已满");
                }
                cartItemValue = new CartItemValue();
                cartItemValue.setQuantity(itemDTO.getQuantity());
                cartItemValue.setSpecOptionIds(itemDTO.getSpecOptionIds());
                cartItemValue.setProductName(product.getName());
                cartItemValue.setImageUrl(product.getImageUrl());
                cartItemValue.setPrice(priceResult.finalPrice);
                cartItemValue.setSpecDesc(priceResult.specDesc);
                cartItemValue.setAddTime(System.currentTimeMillis());
            }

            redisTemplate.opsForHash().put(cartKey, fieldKey, objectMapper.writeValueAsString(cartItemValue));
            redisTemplate.expire(cartKey, 30, TimeUnit.DAYS);

        } catch (JsonProcessingException e) {
            log.error("处理购物车商品JSON序列化失败", e);
            throw new BusinessException("系统繁忙,请稍后重试");
        }
    }

    @Override
    public void updateItemQuantity(Long userId, CartItemDTO itemDTO) {
        if (itemDTO.getQuantity() <= 0) {
            removeItem(userId, itemDTO);
            return;
        }

        String cartKey = RedisConstants.buildKey(RedisConstants.CART_KEY_PATTERN, userId, itemDTO.getShopId());
        String fieldKey = buildFieldKey(itemDTO);

        String valueJson = (String) redisTemplate.opsForHash().get(cartKey, fieldKey);
        if (valueJson == null) {
            throw new BusinessException("购物车中不存在该商品");
        }

        try {
            CartItemValue cartItemValue = objectMapper.readValue(valueJson, CartItemValue.class);
            cartItemValue.setQuantity(itemDTO.getQuantity());
            redisTemplate.opsForHash().put(cartKey, fieldKey, objectMapper.writeValueAsString(cartItemValue));
            redisTemplate.expire(cartKey, CART_TTL_DAYS, TimeUnit.DAYS);
        } catch (JsonProcessingException e) {
            log.error("更新购物车商品数量JSON序列化失败", e);
            throw new BusinessException("系统繁忙,请稍后重试");
        }
    }

    @Override
    public void removeItem(Long userId, CartItemDTO itemKey) {
        String cartKey = RedisConstants.buildKey(RedisConstants.CART_KEY_PATTERN, userId, itemKey.getShopId());
        String fieldKey = buildFieldKey(itemKey);
        redisTemplate.opsForHash().delete(cartKey, fieldKey);
    }

    @Override
    public void clearCart(Long userId, Long shopId) {
        redisTemplate.delete(RedisConstants.buildKey(RedisConstants.CART_KEY_PATTERN, userId, shopId));
    }

    @Override
    public CartResultVO getCart(Long userId, Long shopId) {
        String cartKey = RedisConstants.buildKey(RedisConstants.CART_KEY_PATTERN, userId, shopId);
        CartResultVO resultVO = new CartResultVO();
        resultVO.setShopId(shopId);

        CompletableFuture<List<CartItemVO>> cartItemsFuture = CompletableFuture.supplyAsync(() -> {
            Map<Object, Object> entries = redisTemplate.opsForHash().entries(cartKey);
            if (entries.isEmpty()) {
                return Collections.emptyList();
            }
            return buildCartItemsFromRedis(entries, cartKey, userId);
        }, asyncExecutor);

        CompletableFuture<List<UserCouponVO>> couponsFuture = CompletableFuture.supplyAsync(() -> {
            try {
                return marketingApi.getMyCoupons(userId, shopId);
            } catch (Exception e) {
                log.error("获取用户可用优惠券失败, userId: {}, shopId: {}", userId, shopId, e);
                return Collections.emptyList();
            }
        }, asyncExecutor);

        CompletableFuture.allOf(cartItemsFuture, couponsFuture).join();

        List<CartItemVO> cartItems;
        List<UserCouponVO> availableCoupons;
        try {
            cartItems = cartItemsFuture.get();
            availableCoupons = couponsFuture.get();
        } catch (Exception e) {
            throw new BusinessException("获取购物车数据时发生并发错误");
        }

        if (cartItems.isEmpty()) {
            resultVO.setItems(Collections.emptyList());
            resultVO.setOriginalTotalPrice(BigDecimal.ZERO);
            resultVO.setFinalTotalPrice(BigDecimal.ZERO);
            resultVO.setTotalDiscountAmount(BigDecimal.ZERO);
            return resultVO;
        }

        BigDecimal originalTotalPrice = cartItems.stream()
                .map(item -> item.getPrice().multiply(new BigDecimal(item.getQuantity())))
                .reduce(BigDecimal.ZERO, BigDecimal::add);

        resultVO.setItems(cartItems);
        resultVO.setOriginalTotalPrice(originalTotalPrice);

        CouponCalculationResult couponResult = calculateBestCouponCombination(originalTotalPrice, availableCoupons);

        BigDecimal finalTotalPrice = originalTotalPrice.subtract(couponResult.totalDiscount);
        if (finalTotalPrice.compareTo(new BigDecimal("0.01")) < 0) {
            finalTotalPrice = new BigDecimal("0.01");
        }

        resultVO.setFinalTotalPrice(finalTotalPrice);
        resultVO.setTotalDiscountAmount(couponResult.totalDiscount);
        resultVO.setPlatformCoupon(couponResult.bestPlatformCoupon);
        resultVO.setMerchantCoupon(couponResult.bestMerchantCoupon);

        validateCartItemsAsync(userId, shopId, cartItems);

        return resultVO;
    }

    private CouponCalculationResult calculateBestCouponCombination(BigDecimal totalAmount, List<UserCouponVO> coupons) {
        Map<Integer, List<UserCouponVO>> couponsByType = coupons.stream()
                .collect(Collectors.groupingBy(UserCouponVO::getIssuerType));

        List<UserCouponVO> platformCoupons = couponsByType.getOrDefault(1, Collections.emptyList());
        List<UserCouponVO> merchantCoupons = couponsByType.getOrDefault(2, Collections.emptyList());

        UserCouponVO bestPlatformCoupon = findBestCoupon(totalAmount, platformCoupons);

        BigDecimal amountAfterPlatformCoupon = totalAmount;
        if (bestPlatformCoupon != null) {
            amountAfterPlatformCoupon = totalAmount.subtract(bestPlatformCoupon.getDiscountAmount());
        }

        UserCouponVO bestMerchantCoupon = findBestCoupon(amountAfterPlatformCoupon, merchantCoupons);

        BigDecimal totalDiscount = BigDecimal.ZERO;
        AppliedCouponInfo platformInfo = null;
        AppliedCouponInfo merchantInfo = null;

        if (bestPlatformCoupon != null) {
            totalDiscount = totalDiscount.add(bestPlatformCoupon.getDiscountAmount());
            platformInfo = new AppliedCouponInfo();
            platformInfo.setUserCouponId(bestPlatformCoupon.getId());
            platformInfo.setTitle(bestPlatformCoupon.getTitle());
            platformInfo.setDiscountAmount(bestPlatformCoupon.getDiscountAmount());
        }
        if (bestMerchantCoupon != null) {
            totalDiscount = totalDiscount.add(bestMerchantCoupon.getDiscountAmount());
            merchantInfo = new AppliedCouponInfo();
            merchantInfo.setUserCouponId(bestMerchantCoupon.getId());
            merchantInfo.setTitle(bestMerchantCoupon.getTitle());
            merchantInfo.setDiscountAmount(bestMerchantCoupon.getDiscountAmount());
        }

        return new CouponCalculationResult(totalDiscount, platformInfo, merchantInfo);
    }

    private List<CartItemVO> buildCartItemsFromRedis(Map<Object, Object> entries, String cartKey, Long userId) {
        if (entries.isEmpty()) {
            return Collections.emptyList();
        }

        List<Long> productIds = new ArrayList<>();
        Map<String, CartItemValue> parsedValues = new HashMap<>();

        for (Map.Entry<Object, Object> entry : entries.entrySet()) {
            String fieldKey = (String) entry.getKey();
            try {
                CartItemValue value = objectMapper.readValue((String) entry.getValue(), CartItemValue.class);
                parsedValues.put(fieldKey, value);

                Long productId = Long.parseLong(fieldKey.split(":")[0]);
                productIds.add(productId);
            } catch (Exception e) {
                log.error("反序列化购物车项失败, 将从购物车中移除. userId: {}, fieldKey: {}", userId, fieldKey, e);
                redisTemplate.opsForHash().delete(cartKey, fieldKey);
            }
        }

        if (productIds.isEmpty()) {
            return Collections.emptyList();
        }

        Map<Long, ProductVO> productMap;
        try {
            productMap = productApi.getProductsByIds(productIds.stream().distinct().toList())
                    .stream().collect(Collectors.toMap(ProductVO::getId, Function.identity()));
        } catch (Exception e) {
            log.error("批量获取商品信息失败, userId: {}. 购物车将使用缓存快照展示.", userId, e);
            productMap = Collections.emptyMap();
        }

        List<CartItemVO> cartItems = new ArrayList<>();
        List<String> invalidFieldKeys = new ArrayList<>();

        for (Map.Entry<String, CartItemValue> entry : parsedValues.entrySet()) {
            String fieldKey = entry.getKey();
            CartItemValue value = entry.getValue();
            Long productId = Long.parseLong(fieldKey.split(":")[0]);

            ProductVO product = productMap.get(productId);
            if (!productMap.isEmpty() && product == null) {
                invalidFieldKeys.add(fieldKey);
                continue;
            }

            CartItemVO vo = new CartItemVO();
            vo.setProductId(productId);
            vo.setProductName(value.getProductName());
            vo.setImageUrl(value.getImageUrl());
            vo.setPrice(value.getPrice());
            vo.setSpecDesc(value.getSpecDesc());
            vo.setSpecOptionIds(value.getSpecOptionIds());
            vo.setQuantity(value.getQuantity());
            vo.setStale(false);

            cartItems.add(vo);
        }

        if (!invalidFieldKeys.isEmpty()) {
            redisTemplate.opsForHash().delete(cartKey, invalidFieldKeys.toArray());
            log.info("已清理用户 {} 购物车中 {} 个已删除的商品", userId, invalidFieldKeys.size());
        }

        return cartItems;
    }

    private UserCouponVO findBestCoupon(BigDecimal amount, List<UserCouponVO> coupons) {
        return coupons.stream()
                .filter(coupon -> amount.compareTo(coupon.getThresholdAmount()) >= 0)
                .max(Comparator.comparing(UserCouponVO::getDiscountAmount))
                .orElse(null);
    }

    private ProductVO getProductSnapshot(Long productId) {
        List<ProductVO> products = productApi.getProductsByIds(Collections.singletonList(productId));
        if (CollectionUtils.isEmpty(products)) {
            throw new BusinessException("商品不存在");
        }
        ProductVO product = products.get(0);
        if (product.getStatus() != 1) {
            throw new BusinessException("商品[" + product.getName() + "]已下架");
        }
        return product;
    }

    private void validateCartItemsAsync(Long userId, Long shopId, List<CartItemVO> cartItems) {
        if (cartItems.isEmpty()) {
            return;
        }

        CompletableFuture.runAsync(() -> {
            try {
                log.debug("开始异步校验用户 {} 在店铺 {} 的购物车", userId, shopId);
                List<Long> productIds = cartItems.stream().map(CartItemVO::getProductId).toList();

                Map<Long, ProductVO> latestProductMap = productApi.getProductsByIds(productIds)
                        .stream().collect(Collectors.toMap(ProductVO::getId, Function.identity()));

                boolean needsRedisUpdate = false;

                for (CartItemVO itemVO : cartItems) {
                    ProductVO latestProduct = latestProductMap.get(itemVO.getProductId());

                    if (latestProduct == null || latestProduct.getStatus() != 1) {
                        itemVO.setStale(true);
                        itemVO.setStaleMessage("商品已下架或失效");
                        continue;
                    }

                    PriceAndSpecResult latestPriceResult = calculatePriceAndSpec(latestProduct, itemVO.getSpecOptionIds());

                    if (itemVO.getPrice().compareTo(latestPriceResult.finalPrice) != 0) {
                        itemVO.setStale(true);
                        itemVO.setStaleMessage("价格已变为 ¥" + latestPriceResult.finalPrice);

                        needsRedisUpdate = true;
                        itemVO.setPrice(latestPriceResult.finalPrice);
                    }
                }

                if (needsRedisUpdate) {
                    updateCartItemsInRedis(userId, shopId, cartItems);
                }

                log.debug("异步校验用户 {} 购物车完成", userId);

            } catch (Exception e) {
                log.error("异步校验购物车商品状态失败, userId: {}", userId, e);
            }
        }, asyncExecutor);
    }

    private void updateCartItemsInRedis(Long userId, Long shopId, List<CartItemVO> updatedItems) {
        String cartKey = RedisConstants.buildKey(RedisConstants.CART_KEY_PATTERN, userId, shopId);
        Map<String, String> updatedEntries = new HashMap<>();

        for (CartItemVO itemVO : updatedItems) {
            if (itemVO.isStale() && itemVO.getStaleMessage().contains("价格已变为")) {
                try {
                    CartItemDTO dto = new CartItemDTO();
                    dto.setProductId(itemVO.getProductId());
                    dto.setSpecOptionIds(itemVO.getSpecOptionIds());
                    String fieldKey = buildFieldKey(dto);
                    String valueJson = (String) redisTemplate.opsForHash().get(cartKey, fieldKey);
                    if (valueJson != null) {
                        CartItemValue value = objectMapper.readValue(valueJson, CartItemValue.class);
                        value.setPrice(itemVO.getPrice());
                        updatedEntries.put(fieldKey, objectMapper.writeValueAsString(value));
                    }
                } catch (JsonProcessingException e) {
                    log.error("更新Redis购物车项价格时序列化失败", e);
                }
            }
        }

        if (!updatedEntries.isEmpty()) {
            redisTemplate.opsForHash().putAll(cartKey, updatedEntries);
            log.info("已异步更新用户 {} 在店铺 {} 的购物车中 {} 个商品的价格快照", userId, shopId, updatedEntries.size());
        }
    }

    private String buildFieldKey(CartItemDTO itemDTO) {
        if (itemDTO.getSpecOptionIds() == null || itemDTO.getSpecOptionIds().isEmpty()) {
            return String.valueOf(itemDTO.getProductId());
        }
        Collections.sort(itemDTO.getSpecOptionIds());
        String specKey = itemDTO.getSpecOptionIds().stream()
                .map(String::valueOf)
                .collect(Collectors.joining(","));
        return itemDTO.getProductId() + ":" + specKey;
    }

    private PriceAndSpecResult calculatePriceAndSpec(ProductVO product, List<Long> specOptionIds) {
        BigDecimal finalPrice = product.getBasePrice();
        StringBuilder specDesc = new StringBuilder();

        if (!CollectionUtils.isEmpty(specOptionIds)) {
            Map<Long, SpecOptionVO> optionMap = product.getSpecs().stream()
                    .flatMap(group -> group.getOptions().stream())
                    .collect(Collectors.toMap(SpecOptionVO::getId, Function.identity()));

            for (Long optionId : specOptionIds) {
                SpecOptionVO option = optionMap.get(optionId);
                if (option != null) {
                    finalPrice = finalPrice.add(option.getPriceModifier());
                    if (!specDesc.isEmpty()) {
                        specDesc.append(", ");
                    }
                    specDesc.append(option.getName());
                }
            }
        }
        return new PriceAndSpecResult(finalPrice, specDesc.toString());
    }

    private record PriceAndSpecResult(BigDecimal finalPrice, String specDesc) {
    }

    private record CouponCalculationResult(BigDecimal totalDiscount, AppliedCouponInfo bestPlatformCoupon,
                                           AppliedCouponInfo bestMerchantCoupon) {
    }
}
